package org.redmaple.core.template.dao.hibernate;

import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.redmaple.core.pkstrategy.AbstarctStringGeneratedStrategy;
import org.redmaple.core.pkstrategy.GeneratedStrategy;
import org.redmaple.core.pkstrategy.UUIDGeneratedStrategy;
import org.redmaple.core.template.model.BakModel;
import org.redmaple.core.template.model.BaseModel;
import org.redmaple.core.util.Assert;
import org.redmaple.core.util.DateUtils;

/**
 * HibertnateTemplate模板类
 * 
 * @author Xiangyun
 *
 * @param <T>
 */
@SuppressWarnings("all")
public abstract class RedmapleHibernateTemplate<T extends BaseModel> extends
		DaoImpl<T> {
	
	/**
	 * 主键生成策略
	 */
	protected AbstarctStringGeneratedStrategy generatedStrategy;
	
	/**
	 * 设置主键生成策略。<br/>
	 * 这个方法可由具体类来实现，默认为UUID主键生成策略，格式如下：
	 * 
	 * <br/>
	 * public void setGeneratedStrategy(){<br/>
	 * &emsp;// 采用Time为前缀的UUID主键生成策略<br/>
	 * &emsp;this.generatedStrategy = new UUIDGeneratedStrategy(new TimeGeneratedStrategy(),null);<br/>
	 * }<br/>
	 * <br/>
	 * 
	 * @return
	 */
	protected abstract void setGeneratedStrategy();
	
	public RedmapleHibernateTemplate() {
		
	}

	/**
	 * 保存一个实体到数据库
	 * 
	 * @param entity
	 * @return
	 */
	public T save(T entity) {
		Assert.notNull(entity, entity.getClass().getName() + "实体不能为空");
		this.repairEntity(entity);
		getSession().save(entity);
		return entity;
	}

	/**
	 * 修改一个实体到数据库
	 * 
	 * @param entity
	 * @return
	 */
	public T update(T entity) {
		Assert.notNull(entity, entity.getClass().getName() + "实体不能为空");
		this.repairEntity(entity);
		getSession().update(entity);
		return entity;
	}

	public T saveOrUpdate(T entity) {
		Assert.notNull(entity, entity.getClass().getName() + "实体不能为空");
		this.repairEntity(entity);
		getSession().saveOrUpdate(entity);
		return entity;
	}
	
	public void merge(T entity) {
		this.repairEntity(entity);
		getSession().merge(entity);
	}

	/**
	 * 按ID获取实体对象.
	 * 
	 * @param id
	 *            要查找的实体对象ID
	 * @return 查找到的实体对象
	 */
	public T findOne(String id) {
		Assert.notNull(id, "ID不能为空");
		return (T) getSession().get(getParameterizedTypeName(), id);
	}

	/**
	 * 按id列表获取实体对象列表
	 * 
	 * @param ids
	 *            id列表
	 * @return 获取到的实体对象列表
	 */
	public List<T> findByIds(final Collection<String> ids) {
		return findListByCriteria(Restrictions.in(getIdName(), ids));
	}

	public List<T> findAll() {
		return getSession().createQuery(
				new StringBuilder().append("FROM ")
						.append(getParameterizedTypeName()).toString()).list();
	}

	/**
	 * 删除一个对象，若该对象继承于BakModel类，则设置其删除字段值为true，否则直接从数据可中清除
	 * 
	 * @param id
	 *            要删除的实体对象ID
	 */
	public void delete(String id) {
		T entity = findOne(id);
		delete(entity);
	}

	
	/**
	 * 删除多个对象，若这些对象继承于BakModel类，则设置其删除字段值为true，否则直接从数据可中清除
	 * 
	 * @param ids
	 *            要删除的实体对象ID集合
	 */
	public void deletes(List<String> ids) {
		for(String id : ids) {
			delete(id);
		}
	}
	/**
	 * 删除一个对象，若该对象继承于BakModel类，则设置其删除字段值为true，否则直接从数据可中清除
	 * 
	 * @param entity
	 *            要删除的实体对象
	 */
	public void delete(T entity) {
		if (entity != null) {
			if (entity instanceof BakModel) {
				((BakModel) entity).setDeletedStatus(true);
				((BakModel) entity).setDeleteTime(DateUtils
						.getCurrentTimestamp());
				getSession().update(entity);
			} else {
				getSession().delete(entity);
			}
		}
	}

	/**
	 * 清空该表中的所有数据
	 */
	public void deleteAll() {
		getSession().createQuery(
				new StringBuilder().append("delete from ")
						.append(getParameterizedTypeName()).toString())
				.executeUpdate();
	}

	/**
	 * 删除指定的实体对象
	 * 
	 * @param entities
	 *            要删除的实体对象列表
	 */
	public void deleteAll(Collection<T> entities) {
		for (T t : entities) {
			delete(t);
		}
	}

	/**
	 * 删除数据库中的数据
	 * 
	 * @param entity
	 *            要删除的实体对象
	 */
	public void deleteNoTrace(T entity) {
		getSession().delete(entity);
	}

	/**
	 * 删除数据库中的数据
	 * 
	 * @param id
	 *            要删除的实体对象ID
	 */
	public void deleteNoTrace(String id) {
		T entity = findOne(id);
		deleteNoTrace(entity);
	}

	public void executeUpdate(String hql) {
		getSession().createQuery(hql).executeUpdate();
	}

	/**
	 * 批量保存实体对象
	 * 
	 * @param entities 要保存的实体对象列表
	 */
	public void saveEntities(final List<T> entities) {
		Session session = getSession();
		T entity = null;
		for (int i = 0; i < entities.size(); i++) {
			entity = entities.get(i);
			this.repairEntity(entity);
			session.save(entity);

			if (i % 20 == 0) { // 单次批量操作的数目为20
				session.flush(); // 清理缓存，执行批量插入20条记录的SQL insert语句
				session.clear(); // 清空缓存中的Customer对象
			}
		}
	}

	public Criteria createCriteria(final Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(
				getParameterizedTypeName());
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 根据查询HQL与参数列表创建Query对象. 与find()函数可进行更加灵活的操作.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public Query createQuery(final String hql, final Map<String, ?> values) {
		Assert.hasText(hql, "hql不能为空");
		Query query = getSession().createQuery(hql);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	public Query createQuery(final String hql, final Object... values) {
		Assert.hasText(hql, "hql不能为空");
		Query queryObject = getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				if (values[i] == null) {
					continue;
				}
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject;
	}

	public Query createSQLQuery(final String sql, final Object... values) {
		Assert.hasText(sql, "sql不能为空");
		Query queryObject = getSession().createSQLQuery(sql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject;
	}

	public Query createSQLQuery(final String sql, final Map<String, ?> values) {
		Assert.hasText(sql, "sql不能为空");
		Query query = getSession().createSQLQuery(sql);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	/**
	 * 为Criteria添加distinct transformer. 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	public Criteria distinct(Criteria criteria) {
		criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return criteria;
	}

	/**
	 * 为Query添加distinct transformer. 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	public Query distinct(Query query) {
		query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return query;
	}

	public void executeSql(final String executeSql) {
		Query query = getSession().createSQLQuery(executeSql);
		query.executeUpdate();
	}

	/**
	 * 按Criteria查询对象列表.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 */
	public List<T> findListByCriteria(final Criterion... criterions) {
		Criteria criteria = this.createCriteria(criterions);
		distinct(criteria);
		return criteria.list();
	}

	public List<T> findListByCriteria(final DetachedCriteria detachedCriteria) {
		return findListByCriteria(detachedCriteria, -1, -1);
	}

	public List<T> findListByCriteria(final DetachedCriteria detachedCriteria,
			final int rowStart, final int rowEnd) {
		int pageSize = rowEnd - rowStart;
		Criteria criteria = detachedCriteria
				.getExecutableCriteria(getSession());
		if (pageSize > 0 && rowStart >= 0) {
			criteria.setFirstResult(rowStart).setMaxResults(pageSize);
		}
		distinct(criteria);
		return criteria.list();
	}

	public List<T> findListByQuery(final String hql) {
		return findListByQuery(hql);
	}

	public List<T> findListByQuery(final String hql, final Object... values) {
		return findListByQuery(hql, -1, -1, values);
	}

	public List<T> findListByQuery(final String hql, final int rowStart,
			final int rowEnd, final Object... values) {
		int pageSize = rowEnd - rowStart;
		Query query = createQuery(hql, values);
		if (pageSize > 0 && rowStart >= 0) {
			query.setFirstResult(rowStart).setMaxResults(pageSize);
		}
		distinct(query);
		return query.list();
	}

	public List<T> findListBySQLQuery(final String sql) {
		return findListBySQLQuery(sql, -1, -1, null);
	}

	public List<T> findListBySQLQuery(final String sql, final Object... values) {
		return findListBySQLQuery(sql, -1, -1, values);
	}

	public List<T> findListBySQLQuery(final String sql, final int rowStart,
			final int rowEnd, final Object... values) {
		int pageSize = rowEnd - rowStart;
		Query query = createSQLQuery(sql, values);
		if (pageSize > 0 && rowStart >= 0) {
			query.setFirstResult(rowStart).setMaxResults(pageSize);
		}
		return query.list();
	}

	/**
	 * 按Criteria查询唯一对象.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 */
	public T findUnique(final Criterion... criterions) {
		return (T) createCriteria(criterions).uniqueResult();
	}

	public T findUnique(final DetachedCriteria detachedCriteria) {
		Criteria criteria = detachedCriteria
				.getExecutableCriteria(getSession());
		return (T) criteria.uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 */
	public T findUnique(final String hql, final Object... values) {
		return (T) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public T findUnique(final String hql, final Map<String, ?> values) {
		return (T) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            命名参数,按名称绑定.
	 */
	public T findSQLUnique(final String sql, final Map<String, ?> values) {
		return (T) createSQLQuery(sql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 */
	public T findSQLUnique(final String sql, final Object... values) {
		return (T) createSQLQuery(sql, values).uniqueResult();
	}

	/**
	 * 按属性查找唯一对象, 匹配方式为相等.
	 */
	public T findUniqueBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return (T) createCriteria(criterion).uniqueResult();
	}

	/**
	 * 取得对象的主键名.
	 */
	public String getIdName() {
		ClassMetadata meta = getSessionFactory().getClassMetadata(
				getParameterizedTypeName());
		return meta.getIdentifierPropertyName();
	}
	
	

	/**
	 * 初始化对象. 使用load()方法得到的仅是对象Proxy, 在传到View层前需要进行初始化. 如果传入entity,
	 * 则只初始化entity的直接属性,但不会初始化延迟加载的关联集合和属性. 如需初始化关联属性,需执行:
	 * Hibernate.initialize(user.getRoles())，初始化User的直接属性和关联集合.
	 * Hibernate.initialize
	 * (user.getDescription())，初始化User的直接属性和延迟加载的Description属性.
	 */
	public void initProxyObject(Object proxy) {
		Hibernate.initialize(proxy);
	}

	/**
	 * 判断对象的属性值在数据库内是否唯一.
	 * 
	 * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
	 */
	public boolean isPropertyUnique(final String propertyName,
			final Object newValue, final Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}
		Object object = findUniqueBy(propertyName, newValue);
		return (object == null);
	}

	protected void repairEntities(Collection<T> entities) {
		for (T t : entities) {
			this.repairEntity(t);
		}
	}

	protected void repairEntity(T entity) {
		Timestamp currentTime = DateUtils.getCurrentTimestamp();
		if (StringUtils.isEmpty(entity.getId())) {
			entity.setId(generatedStrategy.getValue());
			entity.setCreateTime(currentTime);
		} else {
			entity.setLastChangeTime(currentTime);
		}
	}

	public long count() {
		return (Long) getSession().createQuery(
				new StringBuilder().append("select count(*) from ")
						.append(getParameterizedTypeName()).toString())
				.uniqueResult();
	}

	/**
	 * 获取参数化类型的class对象
	 * 
	 * @return 参数化类型的class对象
	 */
	protected abstract String getParameterizedTypeName();
}